import os
import re
import shlex
import subprocess
from common.logger import log
from common.benchmark_var import ConfigOptions as ConfigOps, MachineOptions as MachineOps, GlobalEnv
from framework.report.var_report import ReportContentKey as ReportKey

SYS_CMD = {
    ConfigOps.PRODUCT_MODEL: "dmidecode -s system-product-name",
    MachineOps.BIOS_VERSION: "dmidecode -s bios-version",
    MachineOps.ARCH: "uname -m",
    MachineOps.KERNEL: "uname -r",
    MachineOps.CMDLINE: "cat /proc/cmdline",
    MachineOps.PAGE_SIZE: "getconf PAGE_SIZE",
    MachineOps.GCC: "gcc -dumpversion",
    MachineOps.GXX: "g++ -dumpversion",
    MachineOps.PYTHON2: "python2 --version",
    MachineOps.PYTHON3: "python3 --version",
    MachineOps.JAVA: "java -version",
    MachineOps.GLIBC: "ldd --version",
    MachineOps.L1D_CACHE: "lscpu | grep '^L1d' | grep -v \"unknown\"",
    MachineOps.L1I_CACHE: "lscpu | grep '^L1i' | grep -v \"unknown\"",
    MachineOps.L2_CACHE: "lscpu | grep '^L2' | grep -v \"unknown\"",
    MachineOps.L3_CACHE: "lscpu | grep '^L3' | grep -v \"unknown\"",
    ConfigOps.CPU_MODEL: "dmidecode -t 4 | grep Version | awk -F ':' '{print $2}' | uniq",
    MachineOps.CPU_NOMINAL_MHZ: "dmidecode -t 4 | grep \"Current Speed\" | awk -F ':' '{print $2}' | uniq",
    MachineOps.CPU_MAX_MHZ: "dmidecode -t 4 | grep \"Max Speed\" | awk -F ':' '{print $2}' | uniq",
    MachineOps.CPU_BOGOMIPS: "lscpu | grep BogoMIPS | awk -F ':' '{print $2}'",
    MachineOps.CPU_LOGIC_CORES: "cat /proc/cpuinfo | grep processor | wc -l",
    MachineOps.SOCKETS: "lscpu | grep Socket | awk -F ':' '{print $2}'",
    MachineOps.CPU_CORES: "lscpu | grep Core | awk -F ':' '{print $2}'",
    MachineOps.NUMA_TOPOLOGY: "lscpu | grep NUMA",
    MachineOps.MEMORY_TOTAL: "cat /proc/meminfo | grep MemTotal | awk -F ':' '{print $2}'",
    MachineOps.MEMORY_COUNT: "dmidecode 2 | grep -A16 \"Memory Device$\" | grep \"Size\" | grep -v \"No Module\" |"
                             " wc -l",
    MachineOps.MEMORY_CAPACITY: "dmidecode 2 | grep -A16 \"Memory Device$\" | grep \"Size\" | grep -v "
                                "\"No Module Installed\" | grep -v \"Unknown\" | awk -F ':' '{print $2\" |\"}' "
                                "| xargs | sed \"s/|$//\"",
    MachineOps.MEMORY_MANU: "dmidecode 2 | grep -A16 \"Memory Device$\" | grep \"Manufacturer\" "
                            "| grep -v \"Not Specified\" | grep -v \"Unknown\" | grep -v \"NO DIMM\" "
                            "| awk -F ':' '{print $2\" |\"}' | xargs | sed \"s/|$//\"",
    MachineOps.MEMORY_TYPE: "dmidecode 2 | grep -A16 \"Memory Device$\" | grep \"Type:\" | grep -v \"Unknown\" "
                            "| grep -v \"Other\" | awk -F ':' '{print $2\" |\"}' | xargs | sed \"s/|$//\"",
    MachineOps.MEMORY_SPEED: "dmidecode 2 | grep -A16 \"Memory Device$\" | grep \"Speed:\" | grep -v \"Unknown\" "
                             "| awk -F ':' '{print $2\" |\"}' | xargs | sed \"s/|$//\"",
    ConfigOps.OS: "cat /etc/os-release | grep PRETTY_NAME | awk -F '=' '{print $2}' | sed 's/\"//g'",
    MachineOps.HUGE_PAGE_SIZE: "cat /proc/meminfo | grep Hugepagesize | awk -F ':' '{print $2}'",
    MachineOps.RUN_LEVEL: "who -r | awk '{print $2}'",
    MachineOps.BASH: "bash -version | grep \"GNU bash\" | awk -F ':' '{print $1}'"
}

FULL_MACHINE_INFO_CMD = [
    "lscpu", "dmidecode -t 4", "dmidecode -s system-product-name", "dmidecode -s bios-version",
    "cat /proc/cpuinfo", "uname -m", "uname -o", "lsblk", "cat /proc/scsi/scsi", "lsscsi", "lspci",
    "cat /etc/os-release", "printenv LANG", "locale", "cat /proc/cmdline", "getconf PAGE_SIZE",
    "cat /proc/meminfo", "dmidecode 2", "who -r", "bash -version", "gcc -v", "g++ -v", "ld -v",
    "python2 --version", "python3 --version", "java -version", "df -Th", "mount","GPU-info-get"
]

EN_LANG = "en_US.UTF-8"

SYSTEM_KEY = [ReportKey.HARDWARE, ReportKey.SOFTWARE]

USER_CONFIG_FIELDS = [ConfigOps.NETCARDS, ConfigOps.RAID, ConfigOps.MANUFACTURER,
                      ConfigOps.HARDWARE_OTHERS]

class ExecuteCmd:
    @staticmethod
    def execute_cmd(cmd):
        """
        Execution results.
        """
        commands = ExecuteCmd.get_multi_cmd(cmd)
        process_list = []

        try:
            for index, command in enumerate(commands):
                if index == 0:
                    p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
                else:
                    p = subprocess.Popen(command, stdin=process_list[index - 1].stdout, stdout=subprocess.PIPE,
                                         stderr=subprocess.PIPE)
                process_list.append(p)
            out, err = process_list[-1].communicate()
            return_code = process_list[-1].wait()
            if return_code:
                return ""
            if err.decode("utf-8") == "":
                return out.decode("utf-8").strip()
            else:
                return err.decode("utf-8").strip()
        except FileNotFoundError:
            return ""

    @staticmethod
    def get_multi_cmd(cmd):
        """
        Execute multiple shell commands with '|'
        """
        cmd_split = shlex.split(cmd)
        commands = []
        tmp = []
        for value in cmd_split:
            if value != '|':
                tmp.append(value)
            else:
                commands.append(tmp)
                tmp = []
        commands.append(tmp)
        return commands


class MachineInfo:
    def __init__(self):
        self.env_lang = ""
        self.machine_info = {}
        self.disk_list = []
        self.mount_point = ""
        self.sys_file = ""

    def set_env_language(self):
        """
        Set the environment language to English
        """
        self.env_lang = os.getenv(MachineOps.LANG)
        os.environ[MachineOps.LANG] = EN_LANG

    def restore_env_language(self):
        os.environ[MachineOps.LANG] = self.env_lang

    def get_command_result(self):
        for key, cmd in SYS_CMD.items():
            res = ExecuteCmd.execute_cmd(cmd)
            if not res:
                log.warning("Failed to get {}!".format(key))
            self.machine_info[key] = {
                "command": cmd,
                "result": res
            }

    def get_disk(self):
        disk_storage_cmd = "lsblk | grep disk | awk '{print $1\":\"$4}'"
        disk_type_cmd = "ls -oQ /dev/disk/by-id/ | grep -v \"part\" | grep \"\\<%s\\\"\" | grep -v \"\\-0x\" " \
                        "| awk -F '\"' '{print $2}' | awk -F '-' '{print $1\"-\"$2}'"
        disk_info = []

        disk_storage_info = ExecuteCmd.execute_cmd(disk_storage_cmd)
        for info in disk_storage_info.split():
            name, storage = info.split(":")[0], info.split(":")[1]
            model = ExecuteCmd.execute_cmd(disk_type_cmd % name)
            self.disk_list.append(name)
            disk_info.append({MachineOps.NAME: name, MachineOps.CAPACITY: storage, MachineOps.MODEL: model})

        self.machine_info[MachineOps.DISK] = {
            "command": "\n".join([disk_storage_cmd, disk_type_cmd]),
            "result": disk_info
        }

    def get_system(self):
        system_cmd = ["hostname", "uname -o"]
        system_info = []
        for cmd in system_cmd:
            res = ExecuteCmd.execute_cmd(cmd)
            if res:
                system_info.append(res)
        self.machine_info[MachineOps.SYSTEM] = {
            "command": "\n".join(system_cmd),
            "result": "{}: {}".format(*system_info)
        }

    def get_language(self):
        language_cmd = ["locale charmap", "locale | grep LC_COLLATE | awk -F '=' '{print $2}'"]
        language_info = []
        for cmd in language_cmd:
            res = ExecuteCmd.execute_cmd(cmd).strip('"')
            if res:
                language_info.append(res)
        self.machine_info[MachineOps.LANGUAGE] = {
            "command": "\n".join(language_cmd),
            "result": "{} (map=\"{}\", collate=\"{}\")".format(self.env_lang, *language_info)
        }

    def get_thp(self):
        os_name = 'cat /etc/issue'
        res = ExecuteCmd.execute_cmd(os_name)
        if 'Red Hat' in res:
            thp_cmd = 'cat /sys/kernel/mm/redhat_transparent_hugepage/enabled'
        else:
            thp_cmd = 'cat /sys/kernel/mm/transparent_hugepage/enabled'
        switch = ExecuteCmd.execute_cmd(thp_cmd)
        if not switch:
            log.warning('Failed to get transparent hugepage: error path!')
        thp = re.findall(r'(?<=\[)\S+(?=\])', switch)
        self.machine_info[MachineOps.THP] = {
            "command": thp_cmd,
            "result": thp[0] if thp else "",
        }

    def get_file_system(self):
        cmd = "df -Th ./ | tail -n 1 | awk '{print $1\" \"$2}'"
        result = ExecuteCmd.execute_cmd(cmd).split()
        self.mount_point, file_system = result if result else ""
        self.machine_info[MachineOps.FILE_SYSTEM] = {
            "command": cmd,
            "result": file_system
        }

    def get_mount_param(self):
        cmd = "mount | grep %s | awk '{print $6}'"
        if self.mount_point:
            param = ExecuteCmd.execute_cmd(cmd % self.mount_point)
        else:
            param = ""
        param = re.findall(r'(?<=\()\S+(?=\))', param)
        self.machine_info[MachineOps.MOUNT_PARAM] = {
            "command": cmd % self.mount_point if self.mount_point else cmd,
            "result": param[0] if param else ""
        }

    def get_io_scheduler(self):
        system_disk_cmd = "lsblk | grep boot | awk '{print $1}'"
        scheduler_cmd = "cat /sys/block/%s/queue/scheduler"
        system_disk = ExecuteCmd.execute_cmd(system_disk_cmd)
        scheduler, os_disk = "", ""
        for name in self.disk_list:
            if name in system_disk:
                os_disk = name
                scheduler = ExecuteCmd.execute_cmd(scheduler_cmd % name)
                break
        self.machine_info[MachineOps.OS_DISK] = {
            "command": "",
            "result": os_disk
        }
        scheduler = re.findall(r'(?<=\[)\S+(?=\])', scheduler)
        self.machine_info[MachineOps.IO_SCHEDULER] = {
            "command": scheduler_cmd,
            "result": scheduler[0] if scheduler else ""
        }

    def get_binutils(self):
        cmd = "ld -v"
        res = ExecuteCmd.execute_cmd(cmd)
        version = res.split()[-1] if res else ""
        self.machine_info[MachineOps.BINUTILS] = {
            "command": cmd,
            "result": version
        }
    
    def get_gpuinfo(self):
        try:
            cmd = "GPU-info-get"
            res = ExecuteCmd.execute_cmd(cmd)
            self.machine_info[MachineOps.GPU_INFO] = {
                "command": cmd,
                "result": res
            }
        except Exception as err:
                print('An exception happened: ' + str(err))

    def get_displaycards(self):
        try:
            cmdname = "GPU-info-get | grep name | head -n 1 | awk -F ':' '{print $2}' | sed 's/\"//g'" \
                      " | sed 's/\,//g' | sed 's/^[ ]*//g'"
            cmdmanufacturer = "GPU-info-get | grep manufacturerName | head -n 1 | awk -F ':' '{print $2}'" \
                      " | sed 's/\"//g' | sed 's/\,//g' | sed 's/^[ ]*//g'"
            displaycardname = ExecuteCmd.execute_cmd(cmdname)
            displaymanufacturer = ExecuteCmd.execute_cmd(cmdmanufacturer)
            if (not displaycardname) and (not displaymanufacturer):
                res = ''
            elif not displaycardname:
                res = displaycardname
            else:
                res = displaymanufacturer + ' ' + displaycardname
            
            self.machine_info[ConfigOps.DISPLAY_CARDS] = {
                "command": "\n".join([cmdname, cmdmanufacturer]),
                "result": res
            }   
        except Exception as err:
                print('An exception happened: ' + str(err))
    
    def get_soft_others(self):
        cmd_session = "echo $XDG_SESSION_TYPE"
        cmd_use_gles = "echo $USE_GLES"
        other_info = ""
        res_session = os.popen(cmd_session).readline().strip()
        res_use_gles = os.popen(cmd_use_gles).readline().strip()
        if res_use_gles == '1':
            other_info = res_session + '/' + 'gles'
        elif res_use_gles == '0':
            other_info = res_session + '/' + 'gl'
        else:
            other_info = res_session
        self.machine_info[ConfigOps.SOFTWARE_OTHERS] = {
                "command": "\n".join([cmd_session, cmd_use_gles]),
                "result": other_info
        }
        
    def get_machine_info(self):
        try:
            self.set_env_language()
            self.get_command_result()
            self.get_disk()
            self.get_system()
            self.get_language()
            self.get_gpuinfo()
            self.get_thp()
            self.get_file_system()
            self.get_mount_param()
            self.get_io_scheduler()
            self.get_binutils()
            self.restore_env_language()
            self.get_displaycards()
            self.get_soft_others()
        except IndexError as err:
            log.warning("Failed to get machine info, %s" % err)

    @staticmethod
    def get_full_machine_info():
        machine_info = {}
        for cmd in FULL_MACHINE_INFO_CMD:
            res = ExecuteCmd.execute_cmd(cmd)
            machine_info[cmd] = res if res else "Unable to execute {}.".format(cmd)
        return machine_info

    def make_machine_info_file(self):
        machine_info = self.get_full_machine_info()
        self.sys_file = os.path.join(GlobalEnv.GCBS_TMP_DIR, "machine_info.txt")
        if os.path.exists(self.sys_file):
            os.remove(self.sys_file)
        log.info("The environment file is %s." % self.sys_file)
        with open(self.sys_file, "w") as file:
            for cmd, system_info in machine_info.items():
                file.writelines("{}:\n".format(cmd))
                for info in system_info.split("\n"):
                    file.writelines("\t{}\n".format(info))
                file.writelines("\n")

class HwSwInfo:
    @classmethod
    def get_hw_and_sw(cls, system_info, machine_info):
        machining_func = {MachineOps.PYTHON3: cls.get_python3, MachineOps.L1D_CACHE: cls.get_cache,
                          MachineOps.L1I_CACHE: cls.get_cache, MachineOps.GLIBC: cls.get_glibc,
                          MachineOps.L2_CACHE: cls.get_cache, MachineOps.L3_CACHE: cls.get_cache,
                          MachineOps.JAVA: cls.get_java, MachineOps.BASH: cls.get_bash}
        for key in SYSTEM_KEY:
            for option, info in system_info.get(key).items():
                if option in USER_CONFIG_FIELDS or info:
                    continue
                if option in machining_func.keys():
                    system_info.get(key)[option] = machining_func.get(option)(machine_info[option]["result"])
                    continue
                system_info.get(key)[option] = machine_info[option]["result"]
        return system_info

    @staticmethod
    def get_python3(data):
        try:
            return data.split()[-1]
        except Exception as err:
            log.error("Failed to get python3 version, %s" % err)
            return ""

    @staticmethod
    def get_cache(data):
        try:
            return data.split(":")[-1].strip()
        except Exception as err:
            log.error("Failed to get cache info, %s" % err)
            return ""

    @staticmethod
    def get_java(data):
        try:
            version_info = data.split("\n")[0].strip()
            return version_info.split('"')[-2]
        except Exception as err:
            log.error("Failed to get java version, %s" % err)
            return ""

    @staticmethod
    def get_glibc(data):
        try:
            version_info = data.split("\n")[0].strip()
            return version_info.split()[-1]
        except Exception as err:
            log.error("Failed to get glibc version, %s" % err)
            return ""

    @staticmethod
    def get_bash(data):
        try:
            version = re.findall(r"(?<=version )\d+.\d+.\d+", data)
            return version[0] if version else ""
        except Exception as err:
            log.error("Failed to get bash version, %s" % err)
            return ""
